using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using InfiniTec;
using InfiniTec.Exchange.Notifications;
using InfiniTec.Runtime.Serialization;
using Microsoft.Exchange.WebServices.Data;

namespace CalendarSubscriptionApp
{
	[Serializable]
    public sealed class CalendarSubscription : ISerializable
    {
        private readonly List<AppointmentViewModel> _Appointments = new List<AppointmentViewModel>();
        private readonly object _AppointmentsLock = new object();
        private string _CalendarFolderId;
        private ExchangeConnector _ExchangeWebServiceAdapter;
        private SubscriptionManager _SubscriptionManager;
        private string _MailboxId;
        private Subscription _Subscription;

    	[field: NonSerialized]
    	public event EventHandler<AppointmentEventArgs> AppointmentAdded;
		[field: NonSerialized]
		public event EventHandler<AppointmentEventArgs> AppointmentRemoved;

    	public CalendarSubscription(Subscription subscription, ExchangeConnector exchangeWebServiceAdapter, SubscriptionManager subscriptionManager)
        {
            if (subscription == null) throw new ArgumentNullException("subscription");
            if (exchangeWebServiceAdapter == null) throw new ArgumentNullException("exchangeWebServiceAdapter");
            if (subscriptionManager == null) throw new ArgumentNullException("subscriptionManager");

            _Subscription = subscription;
            _MailboxId = _Subscription.Folders.First().Mailbox.Address;
            _ExchangeWebServiceAdapter = exchangeWebServiceAdapter;
            _SubscriptionManager = subscriptionManager;

            BindSubscriptionEvents();
        }

        private CalendarSubscription(SerializationInfo info, StreamingContext context)
        {
            var appointments = info.GetValue<List<AppointmentViewModel>>("Appointments");
            _Appointments.AddRange(appointments);
            _MailboxId = info.GetString("MailboxId");
            _CalendarFolderId = info.GetString("CalendarFolderId");
        }

        public string MailboxId
        {
            get { return _MailboxId; }
        }

        public IEnumerable<AppointmentViewModel> Appointments
        {
            get { return _Appointments; }
        }

        public Subscription Subscription
        {
            get
            {
                return _Subscription;
            }
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Appointments", _Appointments.ToList());
            info.AddValue("MailboxId", MailboxId);
            info.AddValue("CalendarFolderId", _CalendarFolderId);
        }

        [field: NonSerialized]
        public event EventHandler NotificationReceived;

        public void InitializeAfterDeserialization(Subscription subscription, ExchangeConnector exchangeWebServiceAdapter, SubscriptionManager subscriptionManager)
        {
            if (subscription == null) throw new ArgumentNullException("subscription");
            if (exchangeWebServiceAdapter == null) throw new ArgumentNullException("exchangeWebServiceAdapter");
            if (subscriptionManager == null) throw new ArgumentNullException("subscriptionManager");
            _ExchangeWebServiceAdapter = exchangeWebServiceAdapter;
            _Subscription = subscription;
            _SubscriptionManager = subscriptionManager;
            _MailboxId = _Subscription.Folders.First().Mailbox.Address;

            lock (_AppointmentsLock)
            {
            	foreach (var model in _Appointments)
            	{
            		model.InitializeAfterDeserialization(exchangeWebServiceAdapter);
            	}
            }

            BindSubscriptionEvents();
        }

        private void BindSubscriptionEvents()
        {
            _Subscription.ItemCreated += SubscriptionOnItemCreated;
            _Subscription.ItemDeleted += SubscriptionOnItemDeleted;
            _Subscription.ItemModified += SubscriptionOnItemModified;
            _Subscription.ItemMoved += SubscriptionOnItemMoved;
        }

        public void CancelSubscription()
        {
            _SubscriptionManager.RemoveSubscription(this);
        }

        private void InitializeAppointments()
        {
            FolderId folderId = _ExchangeWebServiceAdapter.GetDefaultCalendarFolderId(MailboxId);
            _CalendarFolderId = folderId.UniqueId;
            IEnumerable<Appointment> appointments = _ExchangeWebServiceAdapter.FindAppointments(MailboxId);
			lock (_AppointmentsLock)
			{
				var oldAppointments = _Appointments.ToList();
				_Appointments.Clear();
				oldAppointments.Foreach(model => AppointmentRemoved.Raise(this, new AppointmentEventArgs(model)));
				_Appointments.AddRange(appointments.Select(appointment => new AppointmentViewModel(appointment, _ExchangeWebServiceAdapter, MailboxId)));

			}
        	_Appointments.Foreach(model => AppointmentAdded.Raise(this, new AppointmentEventArgs(model)));
            NotificationReceived.Raise(this);
        }

        private void RefreshAppointment(ItemId id)
        {
            if (id == null) throw new ArgumentNullException("id");

            AppointmentViewModel item;
            lock (_AppointmentsLock)
            {
                item = (from appointment1 in Appointments
                        where appointment1.ItemId.UniqueId == id.UniqueId
                        select appointment1).SingleOrDefault();
            }
            if (item != null)
            {
                item.Refresh();
            }
            else
            {
                // The item id of the modified item could not be found. This happens for recurring items, because all
                // the Appointments collection only contains "Occurrences" of the appointments, but not the recurrence master.
                // Since the occurences are merely virtual items, it is the recurrence master that is actually modified.
                InitializeAppointments();
            }
        }

		public void Start()
		{
			_Subscription.Start();
			InitializeAppointments();
		}

    	private void SubscriptionOnItemMoved(object sender, CopyOrMoveNotificationEventArgs args)
        {
            if (args.ParentFolder.FolderId != _CalendarFolderId)
            {
                RemoveAppointment(args.OldItemId.Id);
            }
            else if (args.ParentFolder.FolderId == _CalendarFolderId)
            {
                RegisterAppointment(args.ItemId.Id);
            }
            NotificationReceived.Raise(this);
        }

        private void SubscriptionOnItemModified(object sender, ItemNotificationEventArgs args)
        {
            RefreshAppointment(args.ItemId.Id);
            NotificationReceived.Raise(this);
        }


        private void RemoveAppointment(string id)
        {
            if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id");

			AppointmentViewModel item;
			
			lock (_AppointmentsLock)
            {
            	item = (from appointment in Appointments
            	        where appointment.ItemId.UniqueId == id
            	        select appointment).SingleOrDefault();

            	if (item != null)
                {
                    _Appointments.Remove(item);
                }
            }

			if (item != null)
			{
				AppointmentRemoved.Raise(this, new AppointmentEventArgs(item));
				return;
			}
			// The item id of the modified item could not be found. This happens for recurring items, because 
			// the Appointments collection only contains "Occurrences" of the appointments, but not the recurrence master.
			// Since the occurences are merely virtual items, it is the recurrence master that is actually modified.
			InitializeAppointments();
        }


        private void RegisterAppointment(string id)
        {
            if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id");

            Appointment appointment = _ExchangeWebServiceAdapter.RefreshAppointment(id, MailboxId);

            // Ignore non appointments 
            if (appointment == null) return;

            if (appointment.AppointmentType == AppointmentType.RecurringMaster)
            {
                // For recurring appointments, a full refresh is needed to get all the occurences of the appointment
                InitializeAppointments();
            }
            else
            {
				var model = new AppointmentViewModel(appointment, _ExchangeWebServiceAdapter, MailboxId);
				lock (_AppointmentsLock)
                {
                	_Appointments.Add(model);
                }
            	AppointmentAdded.Raise(this, new AppointmentEventArgs(model));
            }
        }

        private void SubscriptionOnItemDeleted(object sender, ItemNotificationEventArgs args)
        {
            RemoveAppointment(args.ItemId.Id);
            NotificationReceived.Raise(this);
        }


        private void SubscriptionOnItemCreated(object sender, ItemNotificationEventArgs args)
        {
            RegisterAppointment(args.ItemId.Id);
            NotificationReceived.Raise(this);
        }
    }
}